﻿using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Messages;
using Fine.CRM.Common;
using System;
using ProxyClasses;

namespace Fine.CRM.SolutionToolbox
{
    public class ScheduledProcessPlugin : Plugin
    {
        //To enable informational messages for this plugin create an Extension Setting named
        //Fine.CRM.SolutionToolbox.ScheduleProcessPlugin.Tracelog.EnableInfoMessages and set
        //the value to "true".

        public ScheduledProcessPlugin()
            : base(typeof(ScheduledProcessPlugin))
        {
            base.RegisteredEvents.Add(
                new Tuple<int, string, string, Action<Plugin.LocalPluginContext>>(
                    PipelineStage.PreOp, 
                    MessageName.Update, 
                    fine_scheduledprocess.GetLogicalName(), 
                    new Action<Plugin.LocalPluginContext>(this.ExecuteProcess)));
        }


        protected void ExecuteProcess(LocalPluginContext localContext)
        {
            localContext.TraceLog.EnterMethod("ExecuteProcess");

            if (localContext.PreImage == null)
            {
                throw new Exception("Plugin requires configured PreImage.");
            }

            fine_scheduledprocess scheduledProcess = new fine_scheduledprocess(localContext.MergedTarget);

            if (scheduledProcess.statuscode == fine_scheduledprocess.eStatusReason.Executing)
            {
                int recordsRetrieved = 0;
                int recordsProcessed = 0;

                string fetchXml = scheduledProcess.fine_recordfetchxmldefinition;
                EntityReference processId = scheduledProcess.fine_processid;
                
                if (!string.IsNullOrEmpty(fetchXml)
                    && processId != null)
                {
                    localContext.TraceLog.Trace("Executing Fetch");
                    EntityCollection entityCollection = null;
                    try
                    {
                        entityCollection = localContext.OrganizationService.RetrieveMultiple(new FetchExpression(fetchXml));
                        recordsRetrieved = entityCollection.Entities.Count;
                    }
                    catch (Exception ex)
                    {
                        localContext.TraceLog.Trace(string.Format("Error while retrieving records: {0}", ex.Message), 
                            TraceLog.MessageType.Exception);
                    }
                    
                    if (entityCollection != null)
                    {
                        recordsProcessed = this.ExecuteAssignedProcess(entityCollection, processId, localContext);
                    }
                }
                else
                {
                    localContext.TraceLog.Trace("Scheduled Process is missing Process assignment or FetchXML definition.", 
                        TraceLog.MessageType.Exception);
                }

                localContext.Target.AddUpdateAttribute(fine_scheduledprocess.Properties.fine_recordsretrieved, recordsRetrieved);
                localContext.Target.AddUpdateAttribute(fine_scheduledprocess.Properties.fine_recordsprocessed, recordsProcessed);
                localContext.Target.AddUpdateAttribute(fine_scheduledprocess.Properties.fine_lastexecutiontime, DateTime.Now);

                localContext.TraceLog.Trace("Scheduling Next Execution Time");
                DateTime executionTime = scheduledProcess.fine_nextexecutiontime.HasValue ? scheduledProcess.fine_nextexecutiontime.Value : DateTime.Now;
                
                //if frequency is "run once" then change the status to suspended otherwise
                //update the next execution date.
                if (scheduledProcess.fine_executionfrequency == fine_scheduledprocess.eExecutionFrequency.RunOnce)
                {
                    localContext.Target.AddUpdateAttribute(fine_scheduledprocess.Properties.statuscode,
                        new OptionSetValue((int)fine_scheduledprocess.eStatusReason.Suspended));
                }
                else
                {
                    DateTime nextExecutionTime = DateTime.Now; 
                    switch (scheduledProcess.fine_executionfrequency)
                    {
                        case fine_scheduledprocess.eExecutionFrequency.Daily:
                            nextExecutionTime = executionTime.AddDays(1);
                            break;
                        case fine_scheduledprocess.eExecutionFrequency.Weekly:
                            nextExecutionTime = executionTime.AddDays(7);
                            break;
                        case fine_scheduledprocess.eExecutionFrequency.Monthly:
                            nextExecutionTime = executionTime.AddMonths(1);
                            break;
                        case fine_scheduledprocess.eExecutionFrequency.Quarterly:
                            nextExecutionTime = executionTime.AddMonths(3);
                            break;
                        case fine_scheduledprocess.eExecutionFrequency.Yearly:
                            nextExecutionTime = executionTime.AddYears(1);
                            break;
                    }

                    localContext.Target.AddUpdateAttribute(fine_scheduledprocess.Properties.fine_nextexecutiontime, 
                        nextExecutionTime);
                    localContext.Target.AddUpdateAttribute(fine_scheduledprocess.Properties.statuscode, 
                        new OptionSetValue((int)fine_scheduledprocess.eStatusReason.Scheduled));

                }

            }


            localContext.TraceLog.ExitMethod();
            
        }

        private int ExecuteAssignedProcess(EntityCollection targetRecords, EntityReference processId, LocalPluginContext localContext)
        {
            localContext.TraceLog.EnterMethod("ExecuteAssignedProcess");

            int rv = 0;
            foreach (Entity target in targetRecords.Entities)
            {
                try
                {
                    ExecuteWorkflowRequest executeWorkflowRequest = new ExecuteWorkflowRequest();
                    executeWorkflowRequest.WorkflowId = processId.Id;
                    executeWorkflowRequest.EntityId = target.Id;
                    ExecuteWorkflowResponse executeWorkflowResponse = (ExecuteWorkflowResponse)localContext.OrganizationService.Execute(executeWorkflowRequest);
                    rv++;
                }
                catch (Exception ex)
                {
                    localContext.TraceLog.Trace(string.Format("Error executing workflow {0} on record {1}. {2}", processId.Id, target.Id, ex.Message), 
                        TraceLog.MessageType.Exception);
                }

            }

            localContext.TraceLog.ExitMethod();
            return rv;
        }
    }
}
